﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework.Content.Pipeline;
using Microsoft.Xna.Framework.Content.Pipeline.Processors;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content.Pipeline.Graphics;
using System.IO;

namespace ContentPipelineExtension1
{
    [ContentProcessor]
    class PrismGenerator : ContentProcessor<PrismDefinition, ModelContent>
    {
        List<Vector3> _vertices = new List<Vector3>();
        Vector3[] _baseVertices;
        List<Vector3> _actualVertices = new List<Vector3>();

        int _texCoordId;
        //PrismDefinition _input;

        int _vertexOffsetForBevelTris;

        public override ModelContent Process(PrismDefinition input, ContentProcessorContext context)
        {
            //_input = input;

            if (input.NSides < 3)
            {
                throw new ArgumentException("Must have at least 3 sides");
            }

            if (input.CapRadius <= 0)
            {
                throw new ArgumentException("CapRadius must be > 0");
            }

            var mb = MeshBuilder.StartMesh(input.NSides + "_prism");

            // Along with basic position info, we also want to store texture coordinates in our vertices
            _texCoordId = mb.CreateVertexChannel<Vector2>(VertexChannelNames.TextureCoordinate(0));

            // If we're flatshading, we define the cap vertices twice, so that the bevel faces are not seen by the XNA model generation as sharing
            // edges with the cap, leading to the model having normals that lead to smooth shading rather than faceted, sharp shading
            _vertexOffsetForBevelTris = input.FlatShade ? 1 + input.NSides : 1;

            // Generate base vertices
            _baseVertices = new Vector3[input.NSides];

            Vector3 baseVector = (input.NSides % 2 == 0) ? Vector3.Right : Vector3.Up;
            for (var i = 0; i < input.NSides; i++)
            {
                _baseVertices[i] = Vector3.Transform(baseVector, Matrix.CreateFromAxisAngle(new Vector3(0, 0, 1), ((MathHelper.ToRadians(360) / input.NSides) * -i)));
            }

            // Create actual position definitions
            _actualVertices.Add(Vector3.Zero);

            // Cap vertices
            for (var loop = 0; loop < (input.FlatShade ? 2 : 1); loop++)
            {
                for (var i = 0; i < input.NSides; i++)
                {
                    _actualVertices.Add(_baseVertices[i] * input.CapRadius);
                }
            }


            // Bevel vertices
            if (input.BevelRadius != 0)
            {
                for (var i = 0; i < input.NSides; i++)
                {
                    var v = _baseVertices[i] * input.BevelRadius;

                    v.Z = input.BevelAltitude;

                    _actualVertices.Add(v);
                }
            }

            foreach (var v in _actualVertices)
            {
                mb.CreatePosition(v);
            }

            // Setup the material (don't do anything custom here right now, but I've left this in to demonstrate the extension point)
            var bmc = new BasicMaterialContent();
            mb.SetMaterial(bmc);

            // Define cap vertices (a buncha triangles rotating around the zero point)
            if (input.DisplayCapTop)
            {
                for (var i = 0; i < input.NSides; i++)
                {
                    DefineVertex(mb, 0);
                    DefineVertex(mb, 1 + i);
                    DefineVertex(mb, 1 + ((i + 1) % input.NSides));
                }
            }

            // Define bevel vertices
            if (input.BevelRadius != 0)
            {
                for (var i = 0; i < input.NSides; i++)
                {
                    DefineVertex(mb, _vertexOffsetForBevelTris + i);
                    DefineVertex(mb, _vertexOffsetForBevelTris + input.NSides + i);
                    DefineVertex(mb, _vertexOffsetForBevelTris + input.NSides + ((i + 1) % input.NSides));

                    DefineVertex(mb, _vertexOffsetForBevelTris + i);
                    DefineVertex(mb, _vertexOffsetForBevelTris + input.NSides + ((i + 1) % input.NSides));
                    DefineVertex(mb, _vertexOffsetForBevelTris + ((i + 1) % input.NSides));
                }
            }

            var meshContent = mb.FinishMesh();

            FindVertices(meshContent);
            
            var model = context.Convert<MeshContent, ModelContent>(meshContent, "ModelProcessor");

            var tagData = new Dictionary<string, object>();

            model.Tag = tagData;

            // Store vertex information in the tag data, as an array of Vector3.
            tagData.Add("Vertices", _vertices.ToArray());

            // Also store a custom bounding sphere.
            tagData.Add("BoundingSphere", BoundingSphere.CreateFromPoints(_vertices));

            return model;
        }

        private void DefineVertex(MeshBuilder mb, int i)
        {
            Vector3 av = _actualVertices[i];

            mb.SetVertexChannelData(_texCoordId, new Vector2((float)((av.X + 1) / 2.0), (float)(((-av.Y + 1) / 2.0))));
            mb.AddTriangleVertex(i);
        }

        /// <summary>
        /// Helper for extracting a list of all the vertex positions in a model.
        /// </summary>
        void FindVertices(MeshContent mesh)
        {
            // Is this node a mesh?
            //MeshContent mesh = node as MeshContent;

            if (mesh == null) return;


            // Look up the absolute transform of the mesh.
            var absoluteTransform = mesh.AbsoluteTransform;

            // Loop over all the pieces of geometry in the mesh.
            foreach (var geometry in mesh.Geometry)
            {
                // Loop over all the indices in this piece of geometry.
                // Every group of three indices represents one triangle.
                foreach (var index in geometry.Indices)
                {
                    // Look up the position of this vertex.
                    var vertex = geometry.Vertices.Positions[index];

                    // Transform from local into world space.
                    vertex = Vector3.Transform(vertex, absoluteTransform);

                    // Store this vertex.
                    _vertices.Add(vertex);
                }
            }

            // Recursively scan over the children of this node.
            foreach (MeshContent child in mesh.Children)
            {
                FindVertices(child);
            }
        }
    }
}